/*******************************************************
 * Copyright (c) 2015, ArrayFire
 * All rights reserved.
 *
 * This file is distributed under 3-clause BSD license.
 * The complete license agreement can be obtained at:
 * http://arrayfire.com/licenses/BSD-3-Clause
 ********************************************************/

#pragma once
#include <Param.hpp>
#include <cmath>

namespace cpu {
namespace kernel {

template<typename T>
void hsv2rgb(Param<T> out, CParam<T> in) {
    const af::dim4 dims    = in.dims();
    const af::dim4 strides = in.strides();
    dim_t obStride         = out.strides(3);
    dim_t coff             = strides[2];
    dim_t bCount           = dims[3];

    for (dim_t b = 0; b < bCount; ++b) {
        const T* src = in.get() + b * strides[3];
        T* dst       = out.get() + b * obStride;

        for (dim_t j = 0; j < dims[1]; ++j) {
            dim_t jOff = j * strides[1];
            // j steps along 2nd dimension
            for (dim_t i = 0; i < dims[0]; ++i) {
                // i steps along 1st dimension
                dim_t hIdx = i * strides[0] + jOff;
                dim_t sIdx = hIdx + coff;
                dim_t vIdx = sIdx + coff;

                T H = src[hIdx];
                T S = src[sIdx];
                T V = src[vIdx];

                T R, G, B;
                R = G = B = 0;

                int m = (int)(H * 6);
                T f   = H * 6 - m;
                T p   = V * (1 - S);
                T q   = V * (1 - f * S);
                T t   = V * (1 - (1 - f) * S);

                switch (m % 6) {
                    case 0: R = V, G = t, B = p; break;
                    case 1: R = q, G = V, B = p; break;
                    case 2: R = p, G = V, B = t; break;
                    case 3: R = p, G = q, B = V; break;
                    case 4: R = t, G = p, B = V; break;
                    case 5: R = V, G = p, B = q; break;
                }

                dst[hIdx] = R;
                dst[sIdx] = G;
                dst[vIdx] = B;
            }
        }
    }
}

template<typename T>
void rgb2hsv(Param<T> out, CParam<T> in) {
    const af::dim4 dims    = in.dims();
    const af::dim4 strides = in.strides();
    af::dim4 oStrides      = out.strides();
    dim_t bCount           = dims[3];

    for (dim_t b = 0; b < bCount; ++b) {
        const T* src = in.get() + b * strides[3];
        T* dst       = out.get() + b * oStrides[3];

        for (dim_t j = 0; j < dims[1]; ++j) {
            // j steps along 2nd dimension
            dim_t oj = j * oStrides[1];
            dim_t ij = j * strides[1];

            for (dim_t i = 0; i < dims[0]; ++i) {
                // i steps along 1st dimension
                dim_t oIdx0 = i * oStrides[0] + oj;
                dim_t oIdx1 = oIdx0 + oStrides[2];
                dim_t oIdx2 = oIdx1 + oStrides[2];

                dim_t iIdx0 = i * strides[0] + ij;
                dim_t iIdx1 = iIdx0 + strides[2];
                dim_t iIdx2 = iIdx1 + strides[2];

                T R     = src[iIdx0];
                T G     = src[iIdx1];
                T B     = src[iIdx2];
                T Cmax  = std::max(std::max(R, G), B);
                T Cmin  = std::min(std::min(R, G), B);
                T delta = Cmax - Cmin;

                T H = 0;

                if (Cmax != Cmin) {
                    if (Cmax == R) H = (G - B) / delta + (G < B ? 6 : 0);
                    if (Cmax == G) H = (B - R) / delta + 2;
                    if (Cmax == B) H = (R - G) / delta + 4;
                    H = H / 6.0f;
                }

                dst[oIdx0] = H;
                dst[oIdx1] = (Cmax == 0.0f ? 0 : delta / Cmax);
                dst[oIdx2] = Cmax;
            }
        }
    }
}

}  // namespace kernel
}  // namespace cpu
